from math import *
import sys
import os
import traceback

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL.EXT.texture_filter_anisotropic import *
import pygame
from pygame.locals import *


if sys.platform == 'win32' or sys.platform == 'win64':
    os.environ['SDL_VIDEO_CENTERED'] = '1'


def init_gl(new_screen_size):
    global screen_size  # So that we can use screen_size in other functions
    screen_size = list(new_screen_size)

    glEnable(GL_BLEND)
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

    glEnable(GL_TEXTURE_2D)
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
    glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE)

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
    glEnable(GL_DEPTH_TEST)


def set_view_3D(angle):
    glViewport(0, 0, screen_size[0], screen_size[1])
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(angle, float(screen_size[0]) / float(screen_size[1]), 0.1, 100.0)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()


# Converts from spherical coordinates to rectangular coordinates
def spherical_to_rectangular(center_offset, radius, rot_y, rot_xz):
    return [  # rot_y is rotation about the y axis, rot_xz is elevation above or below the horizontal plane
              center_offset[0] + radius * cos(radians(rot_y)) * cos(radians(rot_xz)),
              center_offset[1] + radius * sin(radians(rot_xz)),
              center_offset[2] + radius * sin(radians(rot_y)) * cos(radians(rot_xz))]


def set_camera_spherical(camera_center, camera_radius, rot_y, rot_xz):
    camera_pos = spherical_to_rectangular(camera_center, camera_radius, rot_y, rot_xz)
    gluLookAt(camera_pos[0], camera_pos[1], camera_pos[2], camera_center[0], camera_center[1], camera_center[2], 0, 1,
              0)


class Texture2D:
    def __init__(self, path):
        surf = pygame.image.load(path)
        data = pygame.image.tostring(surf, "RGBA", 1)

        self.texture = glGenTextures(1)
        self.bind()
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf.get_width(), surf.get_height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, data)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)

    def __del__(self):
        glDeleteTextures(self.texture)

    def bind(self):
        glBindTexture(GL_TEXTURE_2D, self.texture)

    def set_nicest(self):
        self.bind()
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        # Anisotropic Filtering - Makes textures look nice at an angle, but optional
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT))
        #Mipmapping - Makes textures look nice when minified, but optional
        glGenerateMipmap(GL_TEXTURE_2D)


def load_earth():
    global dl_earth, texture_earth

    texture_earth = Texture2D("data/earth.jpg")
    texture_earth.set_nicest()

    dl_earth = glGenLists(1)
    glNewList(dl_earth, GL_COMPILE)

    glPushMatrix()
    glRotatef(-90, 1, 0, 0)
    sphere = gluNewQuadric()
    texture_earth.bind()
    gluQuadricTexture(sphere, GL_TRUE)
    gluSphere(sphere, 1.0, 80, 80)
    gluDeleteQuadric(sphere)
    glPopMatrix()

    glEndList()


def unload_earth():
    global dl_earth, texture_earth
    glDeleteLists(1, dl_earth)
    del dl_earth
    del texture_earth


def draw_earth():
    glCallList(dl_earth)


coordinates = []


def load_points():
    file = open("data/PointList.txt", "r")
    point_data = file.read()
    file.close()

    # Data format is some lines of the form "(#W,#N)"
    for line in point_data.split("\n"):
        line.strip()
        if line == "" or line.startswith("#"): continue

        line = list(map(lambda x: x.strip(), line.split(",")))
        line = list(map(lambda x: x.strip(), [line[0][1:], line[1][:-1]]))

        coordinate = list(map(float, [line[0][:-1], line[1][:-1]]))
        if line[0][-1] == "E": coordinate[0] *= -1
        if line[1][-1] == "S": coordinate[1] *= -1

        coordinates.append(coordinate)


def draw_points():
    r = 1.5
    glBegin(GL_LINES)
    for coordinate in coordinates:
        glVertex3f(0, 0, 0)
        glVertex3fv(spherical_to_rectangular([0, 0, 0], r, coordinate[0] + 90, coordinate[1]))
    glEnd()


pygame.display.init()
pygame.font.init()

screen_size = [800, 600]
multisample = 16  # Set to 0 to disable
icon = pygame.Surface((1, 1))
icon.set_alpha(0)
pygame.display.set_icon(icon)
pygame.display.set_caption("3D World - Ian Mallett - v.2.0.2 - 2012")  # And patch help by Jason Marshall
if multisample:
    pygame.display.gl_set_attribute(GL_MULTISAMPLEBUFFERS, 1)
    pygame.display.gl_set_attribute(GL_MULTISAMPLESAMPLES, multisample)
pygame.display.set_mode(screen_size, OPENGL | DOUBLEBUF)

init_gl(screen_size)

camera_rot = [90, 0]
camera_radius = 5.0
camera_center = [0, 0, 0]


def get_input():
    global camera_rot, camera_radius
    key = pygame.key.get_pressed()
    mpress = pygame.mouse.get_pressed()
    mrel = pygame.mouse.get_rel()
    for event in pygame.event.get():
        if event.type == QUIT: return False
        elif event.type == KEYDOWN:
            if event.key == K_ESCAPE: return False
        elif event.type == MOUSEBUTTONDOWN:
            if event.button == 4: camera_radius -= 0.5
            elif event.button == 5: camera_radius += 0.5
            camera_radius = max([min([camera_radius, 10.0]), 1.2])
    if mpress[0]:
        camera_rot[0] += mrel[0]
        camera_rot[1] += mrel[1]
    return True


def draw():
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    set_view_3D(45)

    set_camera_spherical(camera_center, camera_radius, camera_rot[0], camera_rot[1])

    draw_earth()
    draw_points()

    pygame.display.flip()


def main():
    load_points()
    load_earth()

    clock = pygame.time.Clock()
    while True:
        if not get_input(): break
        draw()
        clock.tick(60)

    unload_earth()

    pygame.quit()
    sys.exit()


if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        tb = sys.exc_info()[2]
        traceback.print_exception(e.__class__, e, tb)
        pygame.quit()
        input()
        sys.exit()
